/*
 * Copyright Terracotta, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.ehcache.config;

import org.ehcache.spi.service.ServiceCreationConfiguration;

import java.util.Collection;
import java.util.function.Predicate;
import java.util.function.UnaryOperator;

/**
 * A fluent builder of {@link Configuration} instances.
 *
 * @param <B> builder sub-type
 */
public interface FluentConfigurationBuilder<B extends FluentConfigurationBuilder<?>> extends Builder<Configuration> {

  /**
   * Return the cache configuration for the given alias.
   *
   * @param alias cache alias
   * @return associated cache configuration
   *
   * @see #withCache(String, CacheConfiguration)
   * @see #withCache(String, Builder)
   * @see #updateCache(String, UnaryOperator)
   * @see #withoutCache(String)
   * @see #updateCaches(UnaryOperator)
   */
  CacheConfiguration<?, ?> getCache(String alias);

  /**
   * Adds the given cache to this configuration.
   * <p>
   * This will overwrite any existing configuration for the cache with this alias.
   *
   * @param alias cache alias
   * @param config cache configuration
   * @return an updated builder
   *
   * @see #getCache(String)
   * @see #withCache(String, Builder)
   * @see #updateCache(String, UnaryOperator)
   * @see #withoutCache(String)
   * @see #updateCaches(UnaryOperator)
   */
  B withCache(String alias, CacheConfiguration<?, ?> config);

  /**
   * Adds the cache configuration built by a builder to this configuration.
   * <p>
   * This will overwrite any existing configuration for the cache with this alias.
   *
   * @param alias cache alias
   * @param builder cache configuration builder
   * @return an updated builder
   *
   * @see #getCache(String)
   * @see #withCache(String, Builder)
   * @see #updateCache(String, UnaryOperator)
   * @see #withoutCache(String)
   * @see #updateCaches(UnaryOperator)
   */
  default B withCache(String alias, Builder<? extends CacheConfiguration<?, ?>> builder) {
    return withCache(alias, builder.build());
  }

  /**
   * Removes the given cache from this configuration.
   *
   * @param alias cache alias
   * @return an updated builder
   *
   * @see #getCache(String)
   * @see #withCache(String, CacheConfiguration)
   * @see #withCache(String, Builder)
   * @see #updateCache(String, UnaryOperator)
   * @see #updateCaches(UnaryOperator)
   */
  B withoutCache(String alias);

  /**
   * Updates the configuration of the identified cache.
   * <p>
   * If a cache exists for the given alias then the following process is performed:
   * <ol>
   *  <li>The configuration is converted to a builder seeded with that configuration.
   *  <li>The builder is then transformed using the {@code update} unary operator.</li>
   *  <li>A new configuration is generated by calling {@code build()} on the resultant builder.</li>
   *  <li>The new configuration is associated with the given alias.</li>
   * </ol>
   * If there is no cache associated with the given {@code alias} then an {@code IllegalStateException} will be thrown.
   *
   * @param alias cache alias
   * @param update configuration mutation function
   * @return an updated builder
   * @throws IllegalArgumentException if no cache configuration exists for {@code alias}
   *
   * @see #getCache(String)
   * @see #withCache(String, CacheConfiguration)
   * @see #withCache(String, Builder)
   * @see #withoutCache(String)
   * @see #updateCaches(UnaryOperator)
   */
  B updateCache(String alias, UnaryOperator<FluentCacheConfigurationBuilder<?, ?, ?>> update) throws IllegalArgumentException;

  /**
   * Updates the configuration of the all caches.
   * <p>
   * For every existing cache the following process is performed:
   * <ol>
   *  <li>The configuration is converted to a builder seeded with that configuration.
   *  <li>The builder is then transformed using the {@code update} unary operator.</li>
   *  <li>A new configuration is generated by calling {@code build()} on the resultant builder.</li>
   *  <li>The new configuration is associated with the given alias.</li>
   * </ol>
   *
   * @param update configuration mutation function
   * @return an updated builder
   *
   * @see #getCache(String)
   * @see #withCache(String, CacheConfiguration)
   * @see #withCache(String, Builder)
   * @see #updateCache(String, UnaryOperator)
   * @see #withoutCache(String)
   */
  B updateCaches(UnaryOperator<FluentCacheConfigurationBuilder<?, ?, ?>> update);

  /**
   * Return the unique service creation configuration of the given type.
   * <p>
   * If there are multiple configuration instances of this type (or subtypes) then an {@code IllegalArgumentException}
   * will be thrown.
   *
   * @param configurationType desired configuration type
   * @param <C> configuration type
   * @return the given configuration type
   * @throws IllegalArgumentException if there are multiple instances of this type
   *
   * @see #getServices(Class)
   * @see #withService(ServiceCreationConfiguration)
   * @see #withService(Builder)
   * @see #withoutServices(Class)
   * @see #withoutServices(Class, Predicate)
   * @see #updateServices(Class, UnaryOperator)
   */
  default <C extends ServiceCreationConfiguration<?, ?>> C getService(Class<C> configurationType) throws IllegalArgumentException {
    Collection<C> services = getServices(configurationType);

    switch (services.size()) {
      case 0:
        return null;
      case 1:
        return services.iterator().next();
      default:
        throw new IllegalArgumentException(configurationType + " does not identify a unique service configuration: " + services);
    }
  }

  /**
   * Return the service creation configurations of the given type.
   *
   * @param configurationType desired configuration type
   * @param <C> configuration type
   * @return all services of this type
   *
   * @see #getService(Class)
   * @see #withService(ServiceCreationConfiguration)
   * @see #withService(Builder)
   * @see #withoutServices(Class)
   * @see #withoutServices(Class, Predicate)
   * @see #updateServices(Class, UnaryOperator)
   */
  <C extends ServiceCreationConfiguration<?, ?>> Collection<C> getServices(Class<C> configurationType);

  /**
   * Adds a service creation configuration to this configuration.
   * <p>
   * This will remove any existing service creation configurations that are incompatible with the supplied one.
   * This removal is equivalent to the following:
   * <pre>{@code configurations.removeIf(
   *     existing -> !config.compatibleWith(existing) || !existing.compatibleWith(config)
   * );}</pre>
   *
   * @param config service creation configuration
   * @return an updated builder
   * @see ServiceCreationConfiguration#compatibleWith(ServiceCreationConfiguration)
   *
   * @see #getService(Class)
   * @see #getServices(Class)
   * @see #withService(Builder)
   * @see #withoutServices(Class)
   * @see #withoutServices(Class, Predicate)
   * @see #updateServices(Class, UnaryOperator)
   */
  B withService(ServiceCreationConfiguration<?, ?> config);

  /**
   * Adds a service creation configuration built by the given builder to this configuration.
   * <p>
   * This will remove any existing configurations that are incompatible with the supplied one.
   *
   * @param builder service creation configuration builder
   * @return an updated builder
   *
   * @see #getService(Class)
   * @see #getServices(Class)
   * @see #withService(ServiceCreationConfiguration)
   * @see #withoutServices(Class)
   * @see #withoutServices(Class, Predicate)
   * @see #updateServices(Class, UnaryOperator)
   */
  default B withService(Builder<? extends ServiceCreationConfiguration<?, ?>> builder) {
    return withService(builder.build());
  }

  /**
   * Removes all service creation configurations of the given type from this configuration.
   *
   * @param clazz service configuration type
   * @return an updated builder
   *
   * @see #getService(Class)
   * @see #getServices(Class)
   * @see #withService(ServiceCreationConfiguration)
   * @see #withService(Builder)
   * @see #withoutServices(Class, Predicate)
   * @see #updateServices(Class, UnaryOperator)
   */
  default B withoutServices(Class<? extends ServiceCreationConfiguration<?, ?>> clazz) {
    return withoutServices(clazz, c -> true);
  }

  /**
   * Removes all service creation configurations of the given type that pass the predicate.
   *
   * @param clazz service configuration type
   * @param predicate predicate controlling removal
   * @param <C> configuration type
   * @return an updated builder
   *
   * @see #getService(Class)
   * @see #getServices(Class)
   * @see #withService(ServiceCreationConfiguration)
   * @see #withService(Builder)
   * @see #withoutServices(Class)
   * @see #updateServices(Class, UnaryOperator)
   */
  <C extends ServiceCreationConfiguration<?, ?>> B withoutServices(Class<C> clazz, Predicate<? super C> predicate);

  /**
   * Updates all service creation configurations of the given type.
   * <p>
   * For each existing service creation configuration instance that is assignment compatible with {@code clazz} the
   * following process is performed:
   * <ol>
   *  <li>The configuration is converted to its detached representations using the
   *     {@link ServiceCreationConfiguration#derive()} method.</li>
   *  <li>The detached representation is transformed using the {@code update} unary operator.</li>
   *  <li>A new configuration is generated by passing the transformed detached representation to the existing
   *     configurations {@link ServiceCreationConfiguration#build(Object)} method.</li>
   *  <li>The new configuration is added to the builders service configuration set.</li>
   * </ol>
   * If there are no service creation configurations assignment compatible with {@code clazz} then an
   * {@code IllegalStateException} will be thrown.
   *
   * @param clazz service creation configuration type
   * @param update configuration mutation function
   * @param <R> configuration detached representation type
   * @param <C> service configuration type
   * @return an updated builder
   * @throws IllegalStateException if no configurations of type {@code C} exist
   *
   * @see #getService(Class)
   * @see #getServices(Class)
   * @see #withService(ServiceCreationConfiguration)
   * @see #withService(Builder)
   * @see #withoutServices(Class)
   * @see #withoutServices(Class, Predicate)
   */
  <R, C extends ServiceCreationConfiguration<?, R>> B updateServices(Class<C> clazz, UnaryOperator<R> update) throws IllegalStateException;

  /**
   * Return the configured classloader instance.
   *
   * @return configured classloader
   *
   * @see #withClassLoader(ClassLoader)
   * @see #withDefaultClassLoader()
   */
  ClassLoader getClassLoader();

  /**
   * Sets the given class loader as the cache manager classloader
   *
   * @param classLoader cache manager classloader
   * @return an updated builder
   *
   * @see #getClassLoader()
   * @see #withDefaultClassLoader()
   */
  B withClassLoader(ClassLoader classLoader);

  /**
   * Removes any provided class loader returning to default behavior
   *
   * @return an updated builder
   *
   * @see #getClassLoader()
   * @see #withClassLoader(ClassLoader)
   */
  B withDefaultClassLoader();
}
